// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

/**
 * 
 */
package appcomp.entities;

import java.net.URI;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.HashMap;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.Exception;

import core.CodeGenerator;
import utility.Pair;

/**
 * @author riche
 *
 */
public class Composite extends Operator {

	private LinkedList<Operator> opList;
	private LinkedList<Connector> conList;
	
	public Composite() {
//		Random r = new Random();
//		String token = Long.toString(Math.abs(r.nextLong()), 36);
//		this.setName(token);
		opList = new LinkedList<Operator>();
		conList = new LinkedList<Connector>();
		includeMap = new HashMap<String, Boolean>();
	}

	public void setCompositeName() {
		if(!opList.isEmpty()) {
			String compname = "";
			for(Operator o : this.opList) {
				compname += o.getName().substring(0, 5);
			}
			this.setName(compname);
		}
	}
	
	/**
	 * @return the conList
	 */
	public LinkedList<Connector> getConList() {
		return conList;
	}

	/**
	 * @return the opList
	 */
	public LinkedList<Operator> getOpList() {
		return opList;
	}
	
	/**
	 * @param o The Operator to add to the list.
	 */
	public void addToOpList(Operator o) {
		opList.add(o);
	}

	/**
	 * @param c The Connector to add to the list.
	 */
	public void addToConList(Connector c) {
		conList.add(c);
	}

	/* (non-Javadoc)
	 * @see appcomp.entities.Operator#consumeConnectors(java.util.List, java.util.List)
	 */
	@Override
	public void consumeConnectors(List<Connector> oldList,
			List<Connector> newList) {
		ArrayList<Connector> deadList = new ArrayList<Connector>();
		for(Operator o : this.opList) {
			for(Connector c : oldList) {
				if(c.getIn() == o) {
					if(this.opList.contains(c.getOut())) {
						this.conList.add(c);
						Port op = o.getPortByName(c.getInName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.INTERNAL, c, op);
						this.portList.add(p);
					}
					else {
						Connector con = Connector.create(new Pair<String, String>(this.getName(), o.getName() + "_" + c.getInName().getRight()), 
								c.getOutName());
						con.setIn(this);
						con.setOut(c.getOut());
						newList.add(con);
						Port op = o.getPortByName(c.getInName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.EXTERNAL, con, op);
						this.portList.add(p);						
					}
					deadList.add(c);
				}
				else if(c.getOut() == o) {
					if(this.opList.contains(c.getIn())) {
						this.conList.add(c);
						Port op = o.getPortByName(c.getOutName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.INTERNAL, c, op);
						this.portList.add(p);
					}
					else {
						Connector con = Connector.create(c.getInName(), 
								new Pair<String, String>(this.getName(), o.getName() + "_" + c.getOutName().getRight()));
						con.setIn(c.getIn());
						con.setOut(this);
						newList.add(con);
						Port op = o.getPortByName(c.getOutName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.EXTERNAL, con, op);
						this.portList.add(p);						
					}
					deadList.add(c);
				}
			}
			for(Connector c : deadList) {
				oldList.remove(c);
			}
		}
	}
	
	public void printPorts() {
		System.out.println(this.getName());
		for(Port p : this.portList) {
			System.out.println("\t" + p.printFullName());
		}
	}

	/* (non-Javadoc)
	 * @see appcomp.entities.Operator#generateCode(core.CodeGenerator)
	 */
	@Override
	public void generateCode(CodeGenerator cg) {
		for(Operator o : this.opList) {
			o.generateCode(cg);
		}
		String filename = "lc_" + this.getName() + "_Operator.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		// Print guards
		file.println("#ifndef LC_" + this.getName().toUpperCase() + "_H_");
		file.println("#define LC_" + this.getName().toUpperCase() + "_H_");
		file.println();

		// Print standard header info
		file.println("#include <assert.h>");
		file.println("#include <lagniappe/Operator.h>");
		file.println("#include <lagniappe/RCon.h>");
		file.println("#include <lagniappe/RData.h>");

		for(Operator o : this.opList) {
			o.generateIncludes(file);
		}
		
	}
}
